"""
Symbol table for C++ code analysis.

Provides symbol resolution, scope management, and namespace handling
for C++ code analysis with support for complex C++ scoping rules.
"""

from typing import Dict, List, Any, Optional, Set, Tuple
from dataclasses import dataclass, field
from enum import Enum
import logging

from ...core.error_handler import log_debug


class SymbolKind(Enum):
    """Types of symbols in C++."""
    FUNCTION = "function"
    METHOD = "method"
    CONSTRUCTOR = "constructor"
    DESTRUCTOR = "destructor"
    VARIABLE = "variable"
    FIELD = "field"
    CLASS = "class"
    STRUCT = "struct"
    UNION = "union"
    ENUM = "enum"
    ENUM_CONSTANT = "enum_constant"
    NAMESPACE = "namespace"
    TYPEDEF = "typedef"
    TYPE_ALIAS = "type_alias"
    TEMPLATE = "template"
    TEMPLATE_PARAMETER = "template_parameter"
    USING_DECLARATION = "using_declaration"
    MACRO = "macro"


@dataclass
class Symbol:
    """Represents a C++ symbol."""
    name: str
    kind: SymbolKind
    qualified_name: str
    location: Dict[str, Any]
    type_info: Dict[str, Any] = field(default_factory=dict)
    scope: str = ""
    namespace: List[str] = field(default_factory=list)
    class_name: Optional[str] = None
    access_level: str = "public"
    is_template: bool = False
    template_info: Dict[str, Any] = field(default_factory=dict)
    attributes: List[str] = field(default_factory=list)
    usr: str = ""
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class Scope:
    """Represents a C++ scope."""
    name: str
    kind: str  # namespace, class, function, block
    parent: Optional['Scope'] = None
    children: List['Scope'] = field(default_factory=list)
    symbols: Dict[str, Symbol] = field(default_factory=dict)
    using_declarations: List[str] = field(default_factory=list)
    using_directives: List[str] = field(default_factory=list)
    template_parameters: List[str] = field(default_factory=list)
    location: Dict[str, Any] = field(default_factory=dict)


class SymbolTable:
    """
    Symbol table for C++ code analysis.
    
    Manages symbols, scopes, and provides symbol resolution
    with support for C++ namespace and class hierarchies.
    """
    
    def __init__(self):
        """Initialize the symbol table."""
        self.reset()
    
    def reset(self):
        """Reset the symbol table for a new file."""
        self.global_scope = Scope(name="::global::", kind="global")
        self.current_scope = self.global_scope
        self.symbols = {}  # USR -> Symbol mapping
        self.qualified_symbols = {}  # qualified_name -> Symbol mapping
        self.scope_stack = [self.global_scope]
        self.namespaces = {}  # namespace_name -> Scope mapping
        self.classes = {}  # class_name -> Scope mapping
        self.template_contexts = []
        
        # Built-in types and symbols
        self._add_builtin_symbols()
    
    def _add_builtin_symbols(self):
        """Add built-in C++ types and symbols."""
        builtin_types = [
            'void', 'bool', 'char', 'signed char', 'unsigned char',
            'wchar_t', 'char16_t', 'char32_t',
            'short', 'unsigned short', 'int', 'unsigned int',
            'long', 'unsigned long', 'long long', 'unsigned long long',
            'float', 'double', 'long double',
            'auto', 'decltype'
        ]
        
        for type_name in builtin_types:
            symbol = Symbol(
                name=type_name,
                kind=SymbolKind.TYPEDEF,
                qualified_name=type_name,
                location={'file': '<builtin>', 'line': 0, 'column': 0},
                scope=self.global_scope.name
            )
            self.add_symbol(symbol)
    
    def enter_scope(self, name: str, kind: str, location: Dict[str, Any] = None) -> Scope:
        """Enter a new scope."""
        scope = Scope(
            name=name,
            kind=kind,
            parent=self.current_scope,
            location=location or {}
        )
        
        self.current_scope.children.append(scope)
        self.scope_stack.append(scope)
        self.current_scope = scope
        
        # Register namespace and class scopes
        if kind == "namespace":
            qualified_name = self._get_qualified_scope_name(scope)
            self.namespaces[qualified_name] = scope
        elif kind in ("class", "struct", "union"):
            qualified_name = self._get_qualified_scope_name(scope)
            self.classes[qualified_name] = scope
        
        log_debug(f"Entered scope: {name} ({kind})")
        return scope
    
    def exit_scope(self) -> Optional[Scope]:
        """Exit the current scope."""
        if len(self.scope_stack) <= 1:
            return None
        
        exited_scope = self.scope_stack.pop()
        self.current_scope = self.scope_stack[-1]
        
        log_debug(f"Exited scope: {exited_scope.name}")
        return exited_scope
    
    def add_symbol(self, symbol: Symbol):
        """Add a symbol to the current scope."""
        # Add to current scope
        self.current_scope.symbols[symbol.name] = symbol
        
        # Add to global symbol tables
        if symbol.usr:
            self.symbols[symbol.usr] = symbol
        self.qualified_symbols[symbol.qualified_name] = symbol
        
        log_debug(f"Added symbol: {symbol.name} ({symbol.kind.value}) to scope {self.current_scope.name}")
    
    def find_symbol(self, name: str, current_scope: Scope = None) -> Optional[Symbol]:
        """
        Find a symbol by name using C++ lookup rules.
        
        Implements C++ name lookup including:
        - Unqualified lookup in current scope and parent scopes
        - Argument-dependent lookup (ADL)
        - Using declarations and directives
        """
        if current_scope is None:
            current_scope = self.current_scope
        
        # First, try unqualified lookup in current scope hierarchy
        scope = current_scope
        while scope:
            # Check direct symbols in this scope
            if name in scope.symbols:
                return scope.symbols[name]
            
            # Check using declarations in this scope
            for using_decl in scope.using_declarations:
                if using_decl.endswith(f"::{name}"):
                    qualified_symbol = self.qualified_symbols.get(using_decl)
                    if qualified_symbol:
                        return qualified_symbol
            
            # Check using directives (brings in entire namespaces)
            for using_dir in scope.using_directives:
                namespace_scope = self.namespaces.get(using_dir)
                if namespace_scope and name in namespace_scope.symbols:
                    return namespace_scope.symbols[name]
            
            scope = scope.parent
        
        # If not found, try qualified lookup
        return self.qualified_symbols.get(name)
    
    def find_qualified_symbol(self, qualified_name: str) -> Optional[Symbol]:
        """Find a symbol by its qualified name."""
        return self.qualified_symbols.get(qualified_name)
    
    def resolve_type(self, type_name: str, current_scope: Scope = None) -> Optional[Symbol]:
        """Resolve a type name to its symbol."""
        # Handle template types
        if '<' in type_name:
            base_type = type_name.split('<')[0]
            return self.find_symbol(base_type, current_scope)
        
        # Handle pointer/reference types
        if type_name.endswith('*') or type_name.endswith('&'):
            base_type = type_name.rstrip('*&').strip()
            return self.find_symbol(base_type, current_scope)
        
        # Handle const/volatile qualifiers
        type_name = type_name.replace('const ', '').replace('volatile ', '').strip()
        
        return self.find_symbol(type_name, current_scope)
    
    def add_using_declaration(self, qualified_name: str):
        """Add a using declaration to current scope."""
        self.current_scope.using_declarations.append(qualified_name)
        log_debug(f"Added using declaration: {qualified_name}")
    
    def add_using_directive(self, namespace_name: str):
        """Add a using directive to current scope."""
        self.current_scope.using_directives.append(namespace_name)
        log_debug(f"Added using directive: {namespace_name}")
    
    def get_symbols_in_scope(self, scope: Scope = None) -> List[Symbol]:
        """Get all symbols visible in a scope."""
        if scope is None:
            scope = self.current_scope
        
        visible_symbols = []
        
        # Collect symbols from scope hierarchy
        current = scope
        while current:
            visible_symbols.extend(current.symbols.values())
            current = current.parent
        
        # Add symbols from using directives
        for using_dir in scope.using_directives:
            namespace_scope = self.namespaces.get(using_dir)
            if namespace_scope:
                visible_symbols.extend(namespace_scope.symbols.values())
        
        return visible_symbols
    
    def get_namespace_symbols(self, namespace_name: str) -> List[Symbol]:
        """Get all symbols in a namespace."""
        namespace_scope = self.namespaces.get(namespace_name)
        if namespace_scope:
            return list(namespace_scope.symbols.values())
        return []
    
    def get_class_members(self, class_name: str) -> List[Symbol]:
        """Get all members of a class."""
        class_scope = self.classes.get(class_name)
        if class_scope:
            return list(class_scope.symbols.values())
        return []
    
    def get_function_overloads(self, function_name: str, scope: Scope = None) -> List[Symbol]:
        """Get all overloads of a function."""
        if scope is None:
            scope = self.current_scope
        
        overloads = []
        for symbol in self.get_symbols_in_scope(scope):
            if (symbol.name == function_name and 
                symbol.kind in (SymbolKind.FUNCTION, SymbolKind.METHOD, 
                               SymbolKind.CONSTRUCTOR, SymbolKind.DESTRUCTOR)):
                overloads.append(symbol)
        
        return overloads
    
    def resolve_function_call(self, function_name: str, 
                             arguments: List[Dict[str, Any]] = None,
                             scope: Scope = None) -> Optional[Symbol]:
        """
        Resolve a function call to the best matching overload.
        
        This is a simplified version of C++ overload resolution.
        """
        overloads = self.get_function_overloads(function_name, scope)
        
        if not overloads:
            return None
        
        if len(overloads) == 1:
            return overloads[0]
        
        # TODO: Implement proper overload resolution based on argument types
        # For now, return the first overload
        return overloads[0]
    
    def get_inheritance_hierarchy(self, class_name: str) -> List[str]:
        """Get the inheritance hierarchy for a class."""
        hierarchy = []
        
        class_symbol = self.find_symbol(class_name)
        if not class_symbol or class_symbol.kind not in (SymbolKind.CLASS, SymbolKind.STRUCT):
            return hierarchy
        
        # Get base classes from metadata
        base_classes = class_symbol.metadata.get('base_classes', [])
        for base_class in base_classes:
            base_name = base_class.get('name', '')
            if base_name:
                hierarchy.append(base_name)
                # Recursively get base class hierarchy
                hierarchy.extend(self.get_inheritance_hierarchy(base_name))
        
        return hierarchy
    
    def is_accessible(self, symbol: Symbol, from_scope: Scope = None) -> bool:
        """Check if a symbol is accessible from a given scope."""
        if from_scope is None:
            from_scope = self.current_scope
        
        # Public symbols are always accessible
        if symbol.access_level == "public":
            return True
        
        # Private symbols are only accessible within the same class
        if symbol.access_level == "private":
            if symbol.class_name:
                return self._is_same_class_scope(from_scope, symbol.class_name)
            return False
        
        # Protected symbols are accessible within the same class or derived classes
        if symbol.access_level == "protected":
            if symbol.class_name:
                return (self._is_same_class_scope(from_scope, symbol.class_name) or
                        self._is_derived_class_scope(from_scope, symbol.class_name))
            return False
        
        return True
    
    def _get_qualified_scope_name(self, scope: Scope) -> str:
        """Get the fully qualified name of a scope."""
        names = []
        current = scope
        while current and current.name != "::global::":
            if current.name:
                names.append(current.name)
            current = current.parent
        
        return "::".join(reversed(names)) if names else "::"
    
    def _is_same_class_scope(self, scope: Scope, class_name: str) -> bool:
        """Check if scope is within the specified class."""
        current = scope
        while current:
            if current.kind in ("class", "struct", "union") and current.name == class_name:
                return True
            current = current.parent
        return False
    
    def _is_derived_class_scope(self, scope: Scope, base_class_name: str) -> bool:
        """Check if scope is within a class derived from the base class."""
        current = scope
        while current:
            if current.kind in ("class", "struct", "union"):
                hierarchy = self.get_inheritance_hierarchy(current.name)
                if base_class_name in hierarchy:
                    return True
            current = current.parent
        return False
    
    def enter_template_context(self, template_params: List[str]):
        """Enter a template context with the given parameters."""
        self.template_contexts.append(template_params)
        self.current_scope.template_parameters.extend(template_params)
    
    def exit_template_context(self):
        """Exit the current template context."""
        if self.template_contexts:
            params = self.template_contexts.pop()
            # Remove template parameters from current scope
            for param in params:
                if param in self.current_scope.template_parameters:
                    self.current_scope.template_parameters.remove(param)
    
    def is_template_parameter(self, name: str) -> bool:
        """Check if a name is a template parameter in the current context."""
        for template_context in self.template_contexts:
            if name in template_context:
                return True
        return False
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get statistics about the symbol table."""
        return {
            'total_symbols': len(self.symbols),
            'qualified_symbols': len(self.qualified_symbols),
            'namespaces': len(self.namespaces),
            'classes': len(self.classes),
            'current_scope_depth': len(self.scope_stack),
            'symbols_by_kind': self._count_symbols_by_kind()
        }
    
    def _count_symbols_by_kind(self) -> Dict[str, int]:
        """Count symbols by their kind."""
        counts = {}
        for symbol in self.symbols.values():
            kind_name = symbol.kind.value
            counts[kind_name] = counts.get(kind_name, 0) + 1
        return counts
    
    def dump_scope_tree(self, scope: Scope = None, indent: int = 0) -> str:
        """Dump the scope tree for debugging."""
        if scope is None:
            scope = self.global_scope
        
        lines = []
        prefix = "  " * indent
        
        lines.append(f"{prefix}{scope.name} ({scope.kind})")
        lines.append(f"{prefix}  Symbols: {len(scope.symbols)}")
        
        if scope.using_declarations:
            lines.append(f"{prefix}  Using declarations: {scope.using_declarations}")
        
        if scope.using_directives:
            lines.append(f"{prefix}  Using directives: {scope.using_directives}")
        
        for child in scope.children:
            lines.append(self.dump_scope_tree(child, indent + 1))
        
        return "\n".join(lines)